﻿using Fast.ExceptionHandling;
using Fast.MultiTenancy;
using Fast.Threading;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Fast.Extensions.Caching
{
    public class DistributedCache<TCacheItem> : DistributedCache<TCacheItem, string>, IDistributedCache<TCacheItem>
        where TCacheItem : class
    {
        public DistributedCache(
            IOptions<FastDistributedCacheOptions> distributedCacheOption,
            IDistributedCache cache,
            IDistributedCacheKeyNormalizer keyNormalizer) : base(distributedCacheOption, cache, keyNormalizer)
        {

        }
    }
    public class DistributedCache<TCacheItem, TCacheKey> : IDistributedCache<TCacheItem, TCacheKey>
        where TCacheItem : class
    {
        protected IDistributedCache Cache { get; }
        protected IDistributedCacheKeyNormalizer KeyNormalizer { get; }
        protected string CacheName { get; set; }
        protected bool IgnoreMultiTenancy { get; set; } = true;

        /// <summary>
        /// 限制可以同时访问资源或资源池的线程数的信号量。
        /// </summary>
        protected SemaphoreSlim SyncSemaphore { get; }

        protected DistributedCacheEntryOptions DefaultCacheOptions;

        private readonly FastDistributedCacheOptions _distributedCacheOption;

        public DistributedCache(
            IOptions<FastDistributedCacheOptions> distributedCacheOption,
            IDistributedCache cache,
            IDistributedCacheKeyNormalizer keyNormalizer)
        {
            _distributedCacheOption = distributedCacheOption.Value;
            Cache = cache;
            KeyNormalizer = keyNormalizer;

            //initialCount:可以同时授予的信号量的初始请求数。(允许多少个线程执行锁起来的代码)
            //maximumCount：可同时授予的信号量的最大请求数。（允许最大允许数，即达到这个数就会上锁）
            SyncSemaphore = new SemaphoreSlim(1, 1);

            SetDefaultOptions();
        }
        /// <summary>
        /// 标准化缓存Key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected virtual string NormalizeKey(TCacheKey key)
        {
            return KeyNormalizer.NormalizeKey(
                new DistributedCacheKeyNormalizeArgs(
                    key.ToString(),
                    CacheName,
                    IgnoreMultiTenancy
                )
            );
        }

        /// <summary>
        /// 设置默认配置选项
        /// </summary>
        protected virtual void SetDefaultOptions()
        {
            CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem));

            //是否忽略多租户
            IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true);

            //配置默认缓存选项
            DefaultCacheOptions = GetDefaultCacheEntryOptions();
        }

        /// <summary>
        /// 获取默认缓存配置选项
        /// </summary>
        /// <returns></returns>
        protected virtual DistributedCacheEntryOptions GetDefaultCacheEntryOptions()
        {
            foreach (var configure in _distributedCacheOption.CacheConfigurators)
            {
                var options = configure.Invoke(CacheName);
                if (options != null)
                {
                    return options;
                }
            }

            return _distributedCacheOption.GlobalCacheEntryOptions;
        }

        /// <summary>
        /// 将字节数组转TCacheItem类型
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        protected virtual TCacheItem ToCacheItem(byte[] bytes)
        {
            if (bytes == null)
            {
                return null;
            }
            return JsonConvert.DeserializeObject<TCacheItem>(Encoding.UTF8.GetString(bytes));
        }

        /// <summary>
        /// 将字节数组数组转 KeyValuePair<TCacheKey, TCacheItem>[]
        /// </summary>
        /// <param name="itemBytes"></param>
        /// <param name="itemKeys"></param>
        /// <returns></returns>
        /// <exception cref="AbpException"></exception>
        protected virtual KeyValuePair<TCacheKey, TCacheItem>[] ToCacheItems(
            byte[][] itemBytes, TCacheKey[] itemKeys
        )
        {
            if (itemBytes.Length != itemKeys.Length)
            {
                throw new FastException("字节数组的数量应与缓存键的数量相同");
            }

            var result = new List<KeyValuePair<TCacheKey, TCacheItem>>();

            for (int i = 0; i < itemKeys.Length; i++)
            {
                result.Add(
                    new KeyValuePair<TCacheKey, TCacheItem>(
                        itemKeys[i],
                        ToCacheItem(itemBytes[i])
                    )
                );
            }

            return result.ToArray();
        }

        /// <summary>
        /// 获取多个缓存值
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        protected virtual KeyValuePair<TCacheKey, TCacheItem>[] GetManyFallback(
            TCacheKey[] keys)
        {
            try
            {
                return keys.Select(key => new KeyValuePair<TCacheKey, TCacheItem>(key, Get(key))).ToArray();
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 获取多个缓存值
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        protected virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyFallbackAsync(
            TCacheKey[] keys,
            CancellationToken token = default)
        {
            try
            {
                var result = new List<KeyValuePair<TCacheKey, TCacheItem>>();

                foreach (var key in keys)
                {
                    result.Add(new KeyValuePair<TCacheKey, TCacheItem>(
                        key,
                        await GetAsync(key, token))
                    );
                }

                return result.ToArray();
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 设置多个缓存值
        /// </summary>
        /// <param name="items"></param>
        /// <param name="options"></param>
        protected virtual void SetManyFallback(
            KeyValuePair<TCacheKey, TCacheItem>[] items,
            DistributedCacheEntryOptions options = null)
        {
            try
            {
                foreach (var item in items)
                {
                    Set(
                        item.Key,
                        item.Value,
                        options
                    );
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 设置多个缓存值
        /// </summary>
        /// <param name="items"></param>
        /// <param name="options"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        protected virtual async Task SetManyFallbackAsync(
            KeyValuePair<TCacheKey, TCacheItem>[] items,
            DistributedCacheEntryOptions options = null,
            CancellationToken token = default)
        {
            try
            {
                foreach (var item in items)
                {
                    await SetAsync(
                        item.Key,
                        item.Value,
                        options,
                        token
                    );
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 获取指定缓存键的缓存项。如果找不到指定缓存键的缓存项，则返回null
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <returns>缓存项或null</returns>
        public virtual TCacheItem Get(
            TCacheKey key
        )
        {
            byte[] cachedBytes;

            try
            {
                cachedBytes = Cache.Get(NormalizeKey(key));
            }
            catch (Exception)
            {
                throw;
            }

            return ToCacheItem(cachedBytes);
        }

        /// <summary>
        /// 获取指定缓存键的缓存项。如果找不到指定缓存键的缓存项，则返回null
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns>缓存项或null</returns>
        public virtual async Task<TCacheItem> GetAsync(
            TCacheKey key,
            CancellationToken token = default
        )
        {
            byte[] cachedBytes;

            try
            {
                cachedBytes = await Cache.GetAsync(NormalizeKey(key), token);
            }
            catch (Exception)
            {
                throw;
            }

            return ToCacheItem(cachedBytes);
        }

        /// <summary>
        /// 获取指定缓存键的多个缓存项
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        /// <returns>缓存项列表</returns>
        public virtual KeyValuePair<TCacheKey, TCacheItem>[] GetMany(
            IEnumerable<TCacheKey> keys
        )
        {
            return GetManyFallback(keys.ToArray());
        }

        /// <summary>
        /// 获取指定缓存键的多个缓存项
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetManyAsync(
           IEnumerable<TCacheKey> keys,
           CancellationToken token = default
        )
        {
            return await GetManyFallbackAsync(keys.ToArray(), token);
        }

        /// <summary>
        /// 获取或添加指定缓存键的缓存项，
        /// 如果未找到指定缓存键的缓存项，则添加由 <paramref name="factory"/> 委托提供的缓存项，并返回提供的缓存项
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="factory">当找不到<paramref name="key" />的缓存项时，factory委托用于提供缓存项 </param>
        /// <param name="optionsFactory">factory委托的缓存选项</param>
        /// <returns></returns>
        public virtual TCacheItem GetOrAdd(
            TCacheKey key,
            Func<TCacheItem> factory,
            Func<DistributedCacheEntryOptions> optionsFactory = null
        )
        {
            //经典双if+locl模式
            var value = Get(key);
            if (value != null)
            {
                return value;
            }

            using (SyncSemaphore.Lock())
            {
                value = Get(key);
                if (value != null)
                {
                    return value;
                }

                value = factory();

                Set(key, value, optionsFactory?.Invoke());
            }

            return value;
        }

        /// <summary>
        /// 获取或添加指定缓存键的缓存项，
        /// 如果未找到指定缓存键的缓存项，则添加由 <paramref name="factory"/> 委托提供的缓存项，并返回提供的缓存项
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="factory">当找不到<paramref name="key" />的缓存项时，factory委托用于提供缓存项 </param>
        /// <param name="optionsFactory">factory委托的缓存选项</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task<TCacheItem> GetOrAddAsync(
            TCacheKey key,
            Func<Task<TCacheItem>> factory,
            Func<DistributedCacheEntryOptions> optionsFactory = null,
            CancellationToken token = default
        )
        {
            var value = await GetAsync(key, token);
            if (value != null)
            {
                return value;
            }

            //异步锁
            using (await SyncSemaphore.LockAsync(token))
            {
                value = await GetAsync(key, token);
                if (value != null)
                {
                    return value;
                }

                value = await factory();

                await SetAsync(key, value, optionsFactory?.Invoke(), token);
            }

            return value;
        }

        /// <summary>
        /// 获取或添加指定缓存键的多个缓存项，
        /// 如果未找到指定缓存键的任何缓存项，则添加由 <paramref name="factory"/> 委托提供的缓存项，并返回提供的缓存项
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        /// <param name="factory">当找不到<paramref name="key" />的缓存项时，factory委托用于提供缓存项</param>
        /// <param name="optionsFactory">factory委托的缓存选项</param>
        /// <returns></returns>
        public virtual KeyValuePair<TCacheKey, TCacheItem>[] GetOrAddMany(
           IEnumerable<TCacheKey> keys,
           Func<IEnumerable<TCacheKey>, List<KeyValuePair<TCacheKey, TCacheItem>>> factory,
           Func<DistributedCacheEntryOptions> optionsFactory = null
        )
        {
            var keyArray = keys.ToArray();

            var result = GetManyFallback(keyArray);

            if (result.All(x => x.Value != null))
            {
                return result;
            }

            //统计不存在缓存的Key
            var notCachedKeys = new List<TCacheKey>();
            var notCachedValuesIndex = new List<int>();
            for (var i = 0; i < keyArray.Length; i++)
            {
                if (result[i].Value != null)
                {
                    continue;
                }

                notCachedKeys.Add(keyArray[i]);
                notCachedValuesIndex.Add(i);
            }
            //用不存在缓存的Key通过委托返回结果
            var notCachedValues = factory.Invoke(notCachedKeys).ToArray();
            var valueQueue = new Queue<KeyValuePair<TCacheKey, TCacheItem>>(notCachedValues);

            //将结果设置至缓存中去
            SetMany(notCachedValues, optionsFactory?.Invoke());

            foreach (var index in notCachedValuesIndex)
            {
                result[index] = valueQueue.Dequeue();
            }

            return result;
        }

        /// <summary>
        /// 获取或添加指定缓存键的多个缓存项，
        /// 如果未找到指定缓存键的任何缓存项，则添加由 <paramref name="factory"/> 委托提供的缓存项，并返回提供的缓存项
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        /// <param name="factory">当找不到<paramref name="key" />的缓存项时，factory委托用于提供缓存项</param>
        /// <param name="optionsFactory">factory委托的缓存选项</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task<KeyValuePair<TCacheKey, TCacheItem>[]> GetOrAddManyAsync(
            IEnumerable<TCacheKey> keys,
            Func<IEnumerable<TCacheKey>, Task<List<KeyValuePair<TCacheKey, TCacheItem>>>> factory,
            Func<DistributedCacheEntryOptions> optionsFactory = null,
            CancellationToken token = default
        )
        {
            var keyArray = keys.ToArray();

            var result = await GetManyFallbackAsync(keyArray, token);

            if (result.All(x => x.Value != null))
            {
                return result;
            }

            //统计不存在缓存的Key
            var notCachedKeys = new List<TCacheKey>();
            var notCachedValuesIndex = new List<int>();
            for (var i = 0; i < keyArray.Length; i++)
            {
                if (result[i].Value != null)
                {
                    continue;
                }

                notCachedKeys.Add(keyArray[i]);
                notCachedValuesIndex.Add(i);
            }
            //用不存在缓存的Key通过委托返回结果
            var notCachedValues = (await factory.Invoke(notCachedKeys)).ToArray();
            var valueQueue = new Queue<KeyValuePair<TCacheKey, TCacheItem>>(notCachedValues);

            //将结果设置至缓存中去
            await SetManyAsync(notCachedValues, optionsFactory?.Invoke(), token);

            foreach (var index in notCachedValuesIndex)
            {
                result[index] = valueQueue.Dequeue();
            }

            return result;
        }

        /// <summary>
        /// 为指定的缓存键设置缓存项值
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="value">要在缓存中设置的缓存项值</param>
        /// <param name="options">值的缓存选项</param>
        public virtual void Set(
            TCacheKey key,
            TCacheItem value,
            DistributedCacheEntryOptions options = null
        )
        {
            try
            {
                Cache.Set(
                    NormalizeKey(key),
                    Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)),
                    options ?? DefaultCacheOptions
                );
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 为指定的缓存键设置缓存项值
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="value">要在缓存中设置的缓存项值</param>
        /// <param name="options">值的缓存选项</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task SetAsync(
            TCacheKey key,
            TCacheItem value,
            DistributedCacheEntryOptions options = null,
            CancellationToken token = default
        )
        {
            try
            {
                await Cache.SetAsync(
                    NormalizeKey(key),
                    Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)),
                    options ?? DefaultCacheOptions,
                    token
                );
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 设置多个缓存项
        /// </summary>
        /// <param name="items">要在缓存上设置的项</param>
        /// <param name="options">值的缓存选项</param>
        public virtual void SetMany(
            IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
            DistributedCacheEntryOptions options = null
        )
        {
            SetManyFallback(items.ToArray(), options);
        }

        /// <summary>
        /// 设置多个缓存项
        /// </summary>
        /// <param name="items">要在缓存上设置的项</param>
        /// <param name="options">值的缓存选项</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task SetManyAsync(
            IEnumerable<KeyValuePair<TCacheKey, TCacheItem>> items,
            DistributedCacheEntryOptions options = null,
            CancellationToken token = default
        )
        {
            await SetManyFallbackAsync(items.ToArray(), options, token);
        }

        /// <summary>
        /// 刷新指定缓存键的缓存值，并重置其滑动过期超时
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        public virtual void Refresh(
            TCacheKey key
        )
        {
            try
            {
                Cache.Refresh(NormalizeKey(key));
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 刷新指定缓存键的缓存值，并重置其滑动过期超时
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task RefreshAsync(
            TCacheKey key,
            CancellationToken token = default
        )
        {
            try
            {
                await Cache.RefreshAsync(NormalizeKey(key), token);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 刷新指定缓存键的缓存值，并重置其滑动过期超时
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        public virtual void RefreshMany(
            IEnumerable<TCacheKey> keys
        )
        {
            try
            {
                foreach (var key in keys)
                {
                    Cache.Refresh(NormalizeKey(key));
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 刷新指定缓存键的缓存值，并重置其滑动过期超时
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task RefreshManyAsync(
            IEnumerable<TCacheKey> keys,
            CancellationToken token = default
        )
        {
            try
            {
                foreach (var key in keys)
                {
                    await Cache.RefreshAsync(NormalizeKey(key), token);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 从缓存中删除指定缓存键的缓存项。
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        public virtual void Remove(
            TCacheKey key
        )
        {
            try
            {
                Cache.Remove(NormalizeKey(key));
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 从缓存中删除指定缓存键的缓存项
        /// </summary>
        /// <param name="key">要从缓存中检索的缓存项的键</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task RemoveAsync(
            TCacheKey key,
            CancellationToken token = default
        )
        {
            try
            {
                await Cache.RemoveAsync(NormalizeKey(key), token);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 从缓存中删除指定缓存键的缓存项
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        public virtual void RemoveMany(
            IEnumerable<TCacheKey> keys
        )
        {
            try
            {
                var keyArray = keys.ToArray();
                foreach (var key in keyArray)
                {
                    Remove(key);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 从缓存中删除指定缓存键的缓存项
        /// </summary>
        /// <param name="keys">要从缓存中检索的缓存项的键</param>
        /// <param name="token">任务的 <see cref="T:System.Threading.CancellationToken" /></param>
        /// <returns></returns>
        public virtual async Task RemoveManyAsync(
            IEnumerable<TCacheKey> keys,
            CancellationToken token = default
        )
        {
            try
            {
                var keyArray = keys.ToArray();
                foreach (var key in keyArray)
                {
                    await RemoveAsync(key, token);
                }
            }
            catch (Exception)
            {

                throw;
            }
        }
    }
}
